//	VirtualDub - Video processing and capture application
//	A/V interface library
//	Copyright (C) 1998-2005 Avery Lee
//
//	This program is free software; you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation; either version 2 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program; if not, write to the Free Software
//	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//
//	This file is the DirectX 9 driver for the video display subsystem.
//	It does traditional point sampled and bilinearly filtered upsampling
//	as well as a special multipass algorithm for emulated bicubic
//	filtering.
//

#include <vd2/system/vdtypes.h>

#define DIRECTDRAW_VERSION 0x0900
#define INITGUID
#include <d3d9.h>
#include <vd2/system/vdtypes.h>
#include <vd2/system/vdalloc.h>
#include <vd2/system/refcount.h>
#include <vd2/system/math.h>
#include <vd2/system/time.h>
#include <vd2/system/vdstl.h>
#include <vd2/system/w32assist.h>
#include <vd2/Riza/direct3d.h>

///////////////////////////////////////////////////////////////////////////

#define VDDEBUG_D3D VDDEBUG

using namespace nsVDD3D9;

///////////////////////////////////////////////////////////////////////////

class VDD3D9Texture : public IVDD3D9Texture, public vdlist_node {
public:
	VDD3D9Texture();
	~VDD3D9Texture();

	int AddRef();
	int Release();

	bool Init(VDD3D9Manager *pManager, IVDD3D9TextureGenerator *pGenerator);
	void Shutdown();

	bool InitVRAMResources();
	void ShutdownVRAMResources();

	const char *GetName() const { return mName.data(); }
	void SetName(const char *name) { mName.assign(name, name+strlen(name)+1); }

	int GetWidth();
	int GetHeight();

	void SetD3DTexture(IDirect3DTexture9 *pTexture);
	IDirect3DTexture9 *GetD3DTexture();

protected:
	vdfastvector<char> mName;
	IDirect3DTexture9 *mpD3DTexture;
	IVDD3D9TextureGenerator *mpGenerator;
	VDD3D9Manager *mpManager;
	int mWidth;
	int mHeight;
	int mRefCount;
	bool mbVRAMTexture;
};

VDD3D9Texture::VDD3D9Texture()
	: mpD3DTexture(NULL)
	, mpGenerator(NULL)
	, mpManager(NULL)
	, mWidth(0)
	, mHeight(0)
	, mRefCount(0)
	, mbVRAMTexture(false)
{
	mListNodeNext = mListNodePrev = this;
}

VDD3D9Texture::~VDD3D9Texture() {
}

int VDD3D9Texture::AddRef() {
	return ++mRefCount;
}

int VDD3D9Texture::Release() {
	int rc = --mRefCount;

	if (!rc) {
		vdlist_base::unlink(*this);
		Shutdown();
		delete this;
	}

	return rc;
}

bool VDD3D9Texture::Init(VDD3D9Manager *pManager, IVDD3D9TextureGenerator *pGenerator) {
	if (mpGenerator)
		mpGenerator->Release();
	if (pGenerator)
		pGenerator->AddRef();
	mpGenerator = pGenerator;
	mpManager = pManager;

	if (mpGenerator) {
		if (!mpGenerator->GenerateTexture(pManager, this))
			return false;
	}

	return true;
}

void VDD3D9Texture::Shutdown() {
	mpManager = NULL;

	if (mpGenerator) {
		mpGenerator->Release();
		mpGenerator = NULL;
	}

	if (mpD3DTexture) {
		mpD3DTexture->Release();
		mpD3DTexture = NULL;
	}
}

bool VDD3D9Texture::InitVRAMResources() {
	if (mbVRAMTexture && !mpD3DTexture && mpGenerator)
		return mpGenerator->GenerateTexture(mpManager, this);

	return true;
}

void VDD3D9Texture::ShutdownVRAMResources() {
	if (mbVRAMTexture) {
		if (mpD3DTexture) {
			mpD3DTexture->Release();
			mpD3DTexture = NULL;
		}
	}
}

int VDD3D9Texture::GetWidth() {
	return mWidth;
}

int VDD3D9Texture::GetHeight() {
	return mHeight;
}

void VDD3D9Texture::SetD3DTexture(IDirect3DTexture9 *pTexture) {
	if (mpD3DTexture)
		mpD3DTexture->Release();
	if (pTexture) {
		pTexture->AddRef();

		D3DSURFACE_DESC desc;
		HRESULT hr = pTexture->GetLevelDesc(0, &desc);

		if (SUCCEEDED(hr)) {
			mWidth = desc.Width;
			mHeight = desc.Height;
			mbVRAMTexture = (desc.Pool == D3DPOOL_DEFAULT);
		} else {
			mWidth = 1;
			mHeight = 1;
			mbVRAMTexture = true;
		}

	}
	mpD3DTexture = pTexture;
}

IDirect3DTexture9 *VDD3D9Texture::GetD3DTexture() {
	return mpD3DTexture;
}

///////////////////////////////////////////////////////////////////////////

class VDD3D9SwapChain : public vdrefcounted<IVDD3D9SwapChain> {
public:
	VDD3D9SwapChain(IDirect3DSwapChain9 *pD3DSwapChain);
	~VDD3D9SwapChain();

	IDirect3DSwapChain9 *GetD3DSwapChain() const { return mpD3DSwapChain; }
protected:
	vdrefptr<IDirect3DSwapChain9> mpD3DSwapChain;
};

VDD3D9SwapChain::VDD3D9SwapChain(IDirect3DSwapChain9 *pD3DSwapChain)
	: mpD3DSwapChain(pD3DSwapChain)
{
}

VDD3D9SwapChain::~VDD3D9SwapChain() {
}

///////////////////////////////////////////////////////////////////////////

static VDCriticalSection g_csVDDirect3D9Managers;
static vdlist<VDD3D9Manager> g_VDDirect3D9Managers;

VDD3D9Manager *VDInitDirect3D9(VDD3D9Client *pClient) {
	VDD3D9Manager *pMgr = NULL;
	bool firstClient = false;

	vdsynchronized(g_csVDDirect3D9Managers) {
		vdlist<VDD3D9Manager>::iterator it(g_VDDirect3D9Managers.begin()), itEnd(g_VDDirect3D9Managers.end());

		VDThreadID tid = VDGetCurrentThreadID();

		for(; it != itEnd; ++it) {
			VDD3D9Manager *mgr = *it;

			if (mgr->GetThreadID() == tid) {
				pMgr = mgr;
				break;
			}
		}

		if (!pMgr) {
			pMgr = new_nothrow VDD3D9Manager;
			if (!pMgr)
				return NULL;

			g_VDDirect3D9Managers.push_back(pMgr);
			firstClient = true;
		}
	}

	bool success = pMgr->Attach(pClient);

	if (success)
		return pMgr;

	if (firstClient) {
		vdsynchronized(g_csVDDirect3D9Managers) {
			g_VDDirect3D9Managers.erase(pMgr);
		}
	}

	return NULL;
}

void VDDeinitDirect3D9(VDD3D9Manager *p, VDD3D9Client *pClient) {
	if (p->Detach(pClient)) {
		vdsynchronized(g_csVDDirect3D9Managers) {
			g_VDDirect3D9Managers.erase(p);
		}
		delete p;
	}
}

VDD3D9Manager::VDD3D9Manager()
	: mhmodDX9(NULL)
	, mpD3D(NULL)
	, mpD3DDevice(NULL)
	, mpD3DRTMain(NULL)
	, mhwndDevice(NULL)
	, mbDeviceValid(false)
	, mbInScene(false)
	, mFullScreenCount(0)
	, mpD3DVB(NULL)
	, mpD3DIB(NULL)
	, mpD3DQuery(NULL)
	, mpD3DVD(NULL)
	, mpImplicitSwapChain(NULL)
	, mRefCount(0)
	, mFenceQueueBase(0)
	, mFenceQueueHeadIndex(0)
{
}

ATOM VDD3D9Manager::sDevWndClass;

VDD3D9Manager::~VDD3D9Manager() {
	VDASSERT(!mRefCount);
}

bool VDD3D9Manager::Attach(VDD3D9Client *pClient) {
	bool bSuccess = false;

	VDASSERT(mClients.find(pClient) == mClients.end());
	mClients.push_back(pClient);

	if (++mRefCount == 1)
		bSuccess = Init();
	else {
		VDASSERT(VDGetCurrentThreadID() == mThreadID);
		bSuccess = CheckDevice();
	}

	if (!bSuccess)
		Detach(pClient);

	return bSuccess;
}

bool VDD3D9Manager::Detach(VDD3D9Client *pClient) {
	VDASSERT(mRefCount > 0);

	VDASSERT(mClients.find(pClient) != mClients.end());
	mClients.erase(mClients.fast_find(pClient));

	if (!--mRefCount) {
		Shutdown();
		return true;
	}

	return false;
}

bool VDD3D9Manager::Init() {
	HINSTANCE hInst = VDGetLocalModuleHandleW32();

	if (!sDevWndClass) {
		WNDCLASS wc;

		wc.cbClsExtra		= 0;
		wc.cbWndExtra		= 0;
		wc.hbrBackground	= NULL;
		wc.hCursor			= NULL;
		wc.hIcon			= NULL;
		wc.hInstance		= hInst;
		wc.lpfnWndProc		= StaticDeviceWndProc;
		wc.lpszClassName	= "RizaD3DDeviceWindow";
		wc.lpszMenuName		= NULL;
		wc.style			= 0;

		sDevWndClass = RegisterClass(&wc);
		if (!sDevWndClass)
			return false;
	}

	mThreadID = VDGetCurrentThreadID();

	mhwndDevice = CreateWindow((LPCTSTR)sDevWndClass, "", WS_POPUP, 0, 0, 0, 0, NULL, NULL, hInst, NULL);
	if (!mhwndDevice) {
		Shutdown();
		return false;
	}

	// attempt to load D3D9.DLL
	mhmodDX9 = LoadLibrary("d3d9.dll");
	if (!mhmodDX9) {
		Shutdown();
		return false;
	}

	IDirect3D9 *(APIENTRY *pDirect3DCreate9)(UINT) = (IDirect3D9 *(APIENTRY *)(UINT))GetProcAddress(mhmodDX9, "Direct3DCreate9");
	if (!pDirect3DCreate9) {
		Shutdown();
		return false;
	}

	// create Direct3D9 object
	mpD3D = pDirect3DCreate9(D3D_SDK_VERSION);
	if (!mpD3D) {
		Shutdown();
		return false;
	}

	// create device
	memset(&mPresentParms, 0, sizeof mPresentParms);
	mPresentParms.Windowed			= TRUE;
	mPresentParms.SwapEffect		= D3DSWAPEFFECT_COPY;
	// BackBufferFormat is set below.
	mPresentParms.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

	HRESULT hr;

	// Look for the NVPerfHUD 2.0 driver
	
	const UINT adapters = mpD3D->GetAdapterCount();
	DWORD dwFlags = D3DCREATE_FPU_PRESERVE | D3DCREATE_NOWINDOWCHANGES;
	UINT adapter = D3DADAPTER_DEFAULT;
	D3DDEVTYPE type = D3DDEVTYPE_HAL;

	for(UINT n=0; n<adapters; ++n) {
		D3DADAPTER_IDENTIFIER9 ident;

		if (SUCCEEDED(mpD3D->GetAdapterIdentifier(n, 0, &ident))) {
			if (strstr(ident.Description, "PerfHUD")) {
				adapter = n;
				type = D3DDEVTYPE_REF;
				break;
			}
		}
	}

	mAdapter = adapter;
	mDevType = type;

	D3DDISPLAYMODE mode;
	hr = mpD3D->GetAdapterDisplayMode(adapter, &mode);
	if (FAILED(hr)) {
		VDDEBUG_D3D("VideoDisplay/DX9: Failed to get current adapter mode.\n");
		Shutdown();
		return false;
	}

#if 1
	mPresentParms.BackBufferWidth	= mode.Width;
	mPresentParms.BackBufferHeight	= mode.Height;
#else
	mPresentParms.BackBufferWidth	= 1600;
	mPresentParms.BackBufferHeight	= 1200;
	mPresentParms.BackBufferCount	= 3;
#endif

	// Make sure we have at least X8R8G8B8 for a texture format
	hr = mpD3D->CheckDeviceFormat(adapter, type, D3DFMT_X8R8G8B8, 0, D3DRTYPE_TEXTURE, D3DFMT_X8R8G8B8);
	if (FAILED(hr)) {
		VDDEBUG_D3D("VideoDisplay/DX9: Device does not support X8R8G8B8 textures.\n");
		Shutdown();
		return false;
	}

	// Make sure we have at least X8R8G8B8 or A8R8G8B8 for a backbuffer format
	mPresentParms.BackBufferFormat	= D3DFMT_A8R8G8B8;
	hr = mpD3D->CheckDeviceFormat(adapter, type, D3DFMT_X8R8G8B8, D3DUSAGE_RENDERTARGET, D3DRTYPE_SURFACE, D3DFMT_A8R8G8B8);
	if (FAILED(hr)) {
		mPresentParms.BackBufferFormat	= D3DFMT_A8R8G8B8;
		hr = mpD3D->CheckDeviceFormat(adapter, type, D3DFMT_X8R8G8B8, D3DUSAGE_RENDERTARGET, D3DRTYPE_SURFACE, D3DFMT_X8R8G8B8);

		if (FAILED(hr)) {
			VDDEBUG_D3D("VideoDisplay/DX9: Device does not support X8R8G8B8 or A8R8G8B8 render targets.\n");
			Shutdown();
			return false;
		}
	}

	// Check if at least vertex shader 1.1 is supported; if not, force SWVP.
	hr = mpD3D->GetDeviceCaps(adapter, type, &mDevCaps);
	if (FAILED(hr)) {
		VDDEBUG_D3D("VideoDisplay/DX9: Couldn't retrieve device caps.\n");
		Shutdown();
		return false;
	}

	if (mDevCaps.VertexShaderVersion >= D3DVS_VERSION(1, 1))
		dwFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;
	else
		dwFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;

	// Create the device.
	hr = mpD3D->CreateDevice(adapter, type, mhwndDevice, dwFlags, &mPresentParms, &mpD3DDevice);
	if (FAILED(hr)) {
		VDDEBUG_D3D("VideoDisplay/DX9: Failed to create device.\n");
		Shutdown();
		return false;
	}

	mbDeviceValid = true;

	// retrieve device caps
	memset(&mDevCaps, 0, sizeof mDevCaps);
	hr = mpD3DDevice->GetDeviceCaps(&mDevCaps);
	if (FAILED(hr)) {
		VDDEBUG_D3D("VideoDisplay/DX9: Failed to retrieve device caps.\n");
		Shutdown();
		return false;
	}

	// Check for Virge/Rage Pro/Riva128
	if (Is3DCardLame()) {
		Shutdown();
		return false;
	}

	const D3DVERTEXELEMENT9 kVertexDecl[]={
		{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
		{ 0, 12, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0 },
		{ 0, 16, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
		{ 0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1 },
		D3DDECL_END()
	};

	hr = mpD3DDevice->CreateVertexDeclaration(kVertexDecl, &mpD3DVD);
	if (FAILED(hr)) {
		Shutdown();
		return false;
	}

	if (!InitVRAMResources()) {
		Shutdown();
		return false;
	}
	return true;
}

bool VDD3D9Manager::InitVRAMResources() {
	// retrieve display mode
	HRESULT hr = mpD3D->GetAdapterDisplayMode(mAdapter, &mDisplayMode);
	if (FAILED(hr)) {
		VDDEBUG_D3D("VideoDisplay/DX9: Failed to get current adapter mode.\n");
		Shutdown();
		return false;
	}

	// retrieve back buffer
	if (!mpD3DRTMain) {
		hr = mpD3DDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &mpD3DRTMain);
		if (FAILED(hr)) {
			ShutdownVRAMResources();
			return false;
		}
	}

	// create vertex buffer
	if (!mpD3DVB) {
		hr = mpD3DDevice->CreateVertexBuffer(kVertexBufferSize * sizeof(Vertex), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX2, D3DPOOL_DEFAULT, &mpD3DVB, NULL);
		if (FAILED(hr)) {
			VDDEBUG_D3D("VideoDisplay/DX9: Failed to create vertex buffer.\n");
			ShutdownVRAMResources();
			return false;
		}
		mVertexBufferPt = 0;
	}

	// create index buffer
	if (!mpD3DIB) {
		hr = mpD3DDevice->CreateIndexBuffer(kIndexBufferSize * sizeof(uint16), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &mpD3DIB, NULL);
		if (FAILED(hr)) {
			VDDEBUG_D3D("VideoDisplay/DX9: Failed to create index buffer.\n");
			ShutdownVRAMResources();
			return false;
		}
		mIndexBufferPt = 0;
	}

	// create flush event
	mbSupportsEventQueries = false;
	if (!mpD3DQuery) {
		hr = mpD3DDevice->CreateQuery(D3DQUERYTYPE_EVENT, NULL);
		if (SUCCEEDED(hr)) {
			mbSupportsEventQueries = true;
			hr = mpD3DDevice->CreateQuery(D3DQUERYTYPE_EVENT, &mpD3DQuery);
		}
	}

	// get implicit swap chain
	if (!mpImplicitSwapChain) {
		vdrefptr<IDirect3DSwapChain9> pD3DSwapChain;
		hr = mpD3DDevice->GetSwapChain(0, ~pD3DSwapChain);
		if (FAILED(hr)) {
			VDDEBUG_D3D("VideoDisplay/DX9: Failed to obtain implicit swap chain.\n");
			ShutdownVRAMResources();
			return false;
		}

		mpImplicitSwapChain = new_nothrow VDD3D9SwapChain(pD3DSwapChain);
		if (!mpImplicitSwapChain) {
			VDDEBUG_D3D("VideoDisplay/DX9: Failed to obtain implicit swap chain.\n");
			ShutdownVRAMResources();
			return false;
		}
		mpImplicitSwapChain->AddRef();
	}

	SharedTextures::iterator it(mSharedTextures.begin()), itEnd(mSharedTextures.end());
	for(; it!=itEnd; ++it) {
		VDD3D9Texture& texture = **it;

		texture.InitVRAMResources();
	}

	return true;
}

void VDD3D9Manager::ShutdownVRAMResources() {
	SharedTextures::iterator it(mSharedTextures.begin()), itEnd(mSharedTextures.end());
	for(; it!=itEnd; ++it) {
		VDD3D9Texture& texture = **it;

		texture.ShutdownVRAMResources();
	}

	if (mpImplicitSwapChain) {
		mpImplicitSwapChain->Release();
		mpImplicitSwapChain = NULL;
	}

	mFenceQueueBase += mFenceQueue.size();
	mFenceQueueHeadIndex = 0;

	while(!mFenceQueue.empty()) {
		IDirect3DQuery9 *pQuery = mFenceQueue.back();
		mFenceQueue.pop_back();

		if (pQuery)
			pQuery->Release();
	}

	while(!mFenceFreeList.empty()) {
		IDirect3DQuery9 *pQuery = mFenceFreeList.back();
		mFenceFreeList.pop_back();

		pQuery->Release();
	}

	if (mpD3DQuery) {
		mpD3DQuery->Release();
		mpD3DQuery = NULL;
	}

	if (mpD3DRTMain) {
		mpD3DRTMain->Release();
		mpD3DRTMain = NULL;
	}

	if (mpD3DIB) {
		mpD3DIB->Release();
		mpD3DIB = NULL;
	}

	if (mpD3DVB) {
		mpD3DVB->Release();
		mpD3DVB = NULL;
	}
}

void VDD3D9Manager::Shutdown() {
	mbDeviceValid = false;

	ShutdownVRAMResources();

	for(vdlist<VDD3D9Client>::iterator it(mClients.begin()), itEnd(mClients.end()); it!=itEnd; ++it) {
		VDD3D9Client& client = **it;

		client.OnPreDeviceReset();
	}

	while(!mSharedTextures.empty()) {
		VDD3D9Texture *pTexture = mSharedTextures.back();
		mSharedTextures.pop_back();

		pTexture->mListNodePrev = pTexture->mListNodeNext = pTexture;
		pTexture->Release();
	}

	if (mpD3DVD) {
		mpD3DVD->Release();
		mpD3DVD = NULL;
	}

	if (mpD3DDevice) {
		mpD3DDevice->Release();
		mpD3DDevice = NULL;
	}

	if (mpD3D) {
		mpD3D->Release();
		mpD3D = NULL;
	}

	if (mhmodDX9) {
		FreeLibrary(mhmodDX9);
		mhmodDX9 = NULL;
	}

	if (mhwndDevice) {
		DestroyWindow(mhwndDevice);
		mhwndDevice = NULL;
	}
}

void VDD3D9Manager::AdjustFullScreen(bool fs) {
	if (fs) {
		++mFullScreenCount;
	} else {
		--mFullScreenCount;
		VDASSERT(mFullScreenCount >= 0);
	}

	bool newState = mFullScreenCount != 0;

	if ((!mPresentParms.Windowed) != newState) {
		D3DDISPLAYMODE dm;
		mpD3DDevice->GetDisplayMode(0, &dm);
		if (newState) {
			UINT count = mpD3D->GetAdapterModeCount(mAdapter, D3DFMT_X8R8G8B8);

			D3DDISPLAYMODE dm2;
			D3DDISPLAYMODE dmbest={0};
			int bestindex = -1;
			
			for(UINT mode=0; mode<count; ++mode) {
				HRESULT hr = mpD3D->EnumAdapterModes(mAdapter, D3DFMT_X8R8G8B8, mode, &dm2);
				if (FAILED(hr))
					break;


				if (dmbest.Width == 0 || abs((int)dm2.Width - (int)dm.Width) + abs((int)dm2.Height - (int)dm.Height) < abs((int)dmbest.Width - (int)dm.Width) + abs((int)dmbest.Height - (int)dm.Height)) {
					dmbest = dm2;
					bestindex = (int)mode;
				}
			}

			if (bestindex < 0) {
				mPresentParms.BackBufferWidth = dm.Width;
				mPresentParms.BackBufferHeight = dm.Height;
				mPresentParms.BackBufferFormat = D3DFMT_X8R8G8B8;
			} else {
				mPresentParms.BackBufferWidth = dmbest.Width;
				mPresentParms.BackBufferHeight = dmbest.Height;
				mPresentParms.BackBufferFormat = D3DFMT_X8R8G8B8;
			}
		} else {
			mPresentParms.BackBufferWidth = dm.Width;
			mPresentParms.BackBufferHeight = dm.Height;
			mPresentParms.BackBufferFormat = D3DFMT_UNKNOWN;
		}

		mPresentParms.Windowed = !newState;
		mPresentParms.SwapEffect = newState ? D3DSWAPEFFECT_DISCARD : D3DSWAPEFFECT_COPY;
		mPresentParms.BackBufferCount = newState ? 1 : 1;
		mPresentParms.PresentationInterval = newState ? D3DPRESENT_INTERVAL_ONE : D3DPRESENT_INTERVAL_IMMEDIATE;
		mPresentParms.FullScreen_RefreshRateInHz = newState ? 60 : 0;

		Reset();
	}
}

bool VDD3D9Manager::Reset() {
	if (!mPresentParms.Windowed) {
		HRESULT hr = mpD3DDevice->TestCooperativeLevel();
		if (FAILED(hr))
			return false;

		// Do not reset the device if we do not have focus!
		HWND hwndFore = GetForegroundWindow();
		if (!hwndFore)
			return false;

		DWORD pid;
		DWORD tid = GetWindowThreadProcessId(hwndFore, &pid);

		if (GetCurrentProcessId() != pid)
			return false;
	}

	for(vdlist<VDD3D9Client>::iterator it(mClients.begin()), itEnd(mClients.end()); it!=itEnd; ++it) {
		VDD3D9Client& client = **it;

		client.OnPreDeviceReset();
	}

	ShutdownVRAMResources();

	HRESULT hr = mpD3DDevice->Reset(&mPresentParms);
	if (FAILED(hr)) {
		mbDeviceValid = false;
		return false;
	}

	mbInScene = false;
	if (!InitVRAMResources())
		return false;

	mbDeviceValid = true;

	for(vdlist<VDD3D9Client>::iterator it(mClients.begin()), itEnd(mClients.end()); it!=itEnd; ++it) {
		VDD3D9Client& client = **it;

		client.OnPostDeviceReset();
	}

	return true;
}

bool VDD3D9Manager::CheckDevice() {
	if (!mpD3DDevice)
		return false;

	if (!mbDeviceValid) {
		HRESULT hr = mpD3DDevice->TestCooperativeLevel();

		if (FAILED(hr)) {
			if (hr != D3DERR_DEVICENOTRESET)
				return false;

			if (!Reset())
				return false;
		}
	}

	return InitVRAMResources();
}

void VDD3D9Manager::AdjustTextureSize(int& texw, int& texh) {
	// use original texture size if device has no restrictions
	if (!(mDevCaps.TextureCaps & (D3DPTEXTURECAPS_NONPOW2CONDITIONAL | D3DPTEXTURECAPS_POW2)))
		return;

	// enforce size limits
	if ((unsigned)texw > mDevCaps.MaxTextureWidth)
		texw = mDevCaps.MaxTextureWidth;

	if ((unsigned)texh > mDevCaps.MaxTextureHeight)
		texh = mDevCaps.MaxTextureHeight;

	// make power of two
	texw += texw - 1;
	texh += texh - 1;

	while(int tmp = texw & (texw-1))
		texw = tmp;
	while(int tmp = texh & (texh-1))
		texh = tmp;

	// enforce aspect ratio
	if (mDevCaps.MaxTextureAspectRatio) {
		while(texw * (int)mDevCaps.MaxTextureAspectRatio < texh)
			texw += texw;
		while(texh * (int)mDevCaps.MaxTextureAspectRatio < texw)
			texh += texh;
	}
}

bool VDD3D9Manager::IsTextureFormatAvailable(D3DFORMAT format) {
	HRESULT hr = mpD3D->CheckDeviceFormat(mAdapter, mDevType, mDisplayMode.Format, 0, D3DRTYPE_TEXTURE, format);

	return SUCCEEDED(hr);
}

void VDD3D9Manager::ClearRenderTarget(IDirect3DTexture9 *pTexture) {
	IDirect3DSurface9 *pRTSurface;
	if (FAILED(pTexture->GetSurfaceLevel(0, &pRTSurface)))
		return;
	HRESULT hr = mpD3DDevice->SetRenderTarget(0, pRTSurface);
	pRTSurface->Release();

	if (FAILED(hr))
		return;

	if (SUCCEEDED(mpD3DDevice->BeginScene())) {
		mpD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET, 0, 0.f, 0);
		mpD3DDevice->EndScene();
	}
}

void VDD3D9Manager::ResetBuffers() {
	mVertexBufferPt = 0;
	mIndexBufferPt = 0;
}

Vertex *VDD3D9Manager::LockVertices(unsigned vertices) {
	VDASSERT(vertices <= kVertexBufferSize);
	if (mVertexBufferPt + vertices > kVertexBufferSize) {
		mVertexBufferPt = 0;
	}

	mVertexBufferLockSize = vertices;

	void *p;
	HRESULT hr;
	for(;;) {
		hr = mpD3DVB->Lock(mVertexBufferPt * sizeof(Vertex), mVertexBufferLockSize * sizeof(Vertex), &p, mVertexBufferPt ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD);
		if (hr != D3DERR_WASSTILLDRAWING)
			break;
		Sleep(1);
	}
	if (FAILED(hr)) {
		VDASSERT(false);
		return NULL;
	}

	return (Vertex *)p;
}

void VDD3D9Manager::UnlockVertices() {
	mVertexBufferPt += mVertexBufferLockSize;

	VDVERIFY(SUCCEEDED(mpD3DVB->Unlock()));
}

uint16 *VDD3D9Manager::LockIndices(unsigned indices) {
	VDASSERT(indices <= kIndexBufferSize);
	if (mIndexBufferPt + indices > kIndexBufferSize) {
		mIndexBufferPt = 0;
	}

	mIndexBufferLockSize = indices;

	void *p;
	HRESULT hr;
	for(;;) {
		hr = mpD3DIB->Lock(mIndexBufferPt * sizeof(uint16), mIndexBufferLockSize * sizeof(uint16), &p, mIndexBufferPt ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD);
		if (hr != D3DERR_WASSTILLDRAWING)
			break;
		Sleep(1);
	}
	if (FAILED(hr)) {
		VDASSERT(false);
		return NULL;
	}

	return (uint16 *)p;
}

void VDD3D9Manager::UnlockIndices() {
	mIndexBufferPt += mIndexBufferLockSize;

	VDVERIFY(SUCCEEDED(mpD3DIB->Unlock()));
}

bool VDD3D9Manager::BeginScene() {
	if (!mbInScene) {
		HRESULT hr = mpD3DDevice->BeginScene();

		if (FAILED(hr)) {
			VDDEBUG_D3D("VideoDisplay/DX9: BeginScene() failed! hr = %08x\n", hr);
			return false;
		}

		mbInScene = true;
	}

	return true;
}

bool VDD3D9Manager::EndScene() {
	if (mbInScene) {
		mbInScene = false;
		HRESULT hr = mpD3DDevice->EndScene();

		if (FAILED(hr)) {
			VDDEBUG_D3D("VideoDisplay/DX9: EndScene() failed! hr = %08x\n", hr);
			return false;
		}
	}

	return true;
}

void VDD3D9Manager::Flush() {
	if (mpD3DQuery) {
		HRESULT hr = mpD3DQuery->Issue(D3DISSUE_END);
		if (SUCCEEDED(hr)) {
			mpD3DQuery->GetData(NULL, 0, D3DGETDATA_FLUSH);
		}
	}
}

void VDD3D9Manager::Finish() {
	if (mpD3DQuery) {
		HRESULT hr = mpD3DQuery->Issue(D3DISSUE_END);
		if (SUCCEEDED(hr)) {
			while(S_FALSE == mpD3DQuery->GetData(NULL, 0, D3DGETDATA_FLUSH))
				::Sleep(1);
		}
	}
}

uint32 VDD3D9Manager::InsertFence() {
	IDirect3DQuery9 *pQuery = NULL;
	if (mFenceFreeList.empty()) {
		HRESULT hr = mpD3DDevice->CreateQuery(D3DQUERYTYPE_EVENT, &pQuery);
		if (FAILED(hr)) {
			VDASSERT(!"Unable to create D3D query.");
			pQuery = NULL;
		}
	} else {
		pQuery = mFenceFreeList.back();
		mFenceFreeList.pop_back();
	}

	uint32 id = (mFenceQueueBase + (uint32)mFenceQueue.size()) | 0x80000000;
	mFenceQueue.push_back(pQuery);

	if (pQuery) {
		HRESULT hr = pQuery->Issue(D3DISSUE_END);
		if (SUCCEEDED(hr))
			pQuery->GetData(NULL, 0, D3DGETDATA_FLUSH);
	}

	return id;
}

void VDD3D9Manager::WaitFence(uint32 id) {
	while(!IsFencePassed(id))
		::Sleep(1);
}

bool VDD3D9Manager::IsFencePassed(uint32 id) {
	if (!id)
		return true;

	uint32 offset = (id - mFenceQueueBase) & 0x7fffffff;

	uint32 fenceQueueSize = (uint32)mFenceQueue.size();
	if (offset >= fenceQueueSize)
		return true;

	IDirect3DQuery9 *pQuery = mFenceQueue[offset];
	if (pQuery) {
		if (S_FALSE == pQuery->GetData(NULL, 0, D3DGETDATA_FLUSH))
			return false;

		mFenceFreeList.push_back(pQuery);
		mFenceQueue[offset] = NULL;
	}

	while(mFenceQueueHeadIndex < fenceQueueSize) {
		IDirect3DQuery9 *pQuery = mFenceQueue[mFenceQueueHeadIndex];
		if (pQuery) {
			if (S_FALSE == pQuery->GetData(NULL, 0, 0))
				break;

			mFenceFreeList.push_back(pQuery);
			mFenceQueue[mFenceQueueHeadIndex] = NULL;
		}

		++mFenceQueueHeadIndex;
	}

	if (mFenceQueueHeadIndex >= 64 && mFenceQueueHeadIndex+mFenceQueueHeadIndex >= fenceQueueSize) {
		mFenceQueueBase += mFenceQueueHeadIndex;
		mFenceQueue.erase(mFenceQueue.begin(), mFenceQueue.begin() + mFenceQueueHeadIndex);
		mFenceQueueHeadIndex = 0;
	}

	return true;
}

HRESULT VDD3D9Manager::DrawArrays(D3DPRIMITIVETYPE type, UINT vertStart, UINT primCount) {
	HRESULT hr = mpD3DDevice->DrawPrimitive(type, mVertexBufferPt - mVertexBufferLockSize + vertStart, primCount);

	VDASSERT(SUCCEEDED(hr));

	return hr;
}

HRESULT VDD3D9Manager::DrawElements(D3DPRIMITIVETYPE type, UINT vertStart, UINT vertCount, UINT idxStart, UINT primCount) {
	// The documentation for IDirect3DDevice9::DrawIndexedPrimitive() was probably
	// written under a hallucinogenic state.

	HRESULT hr = mpD3DDevice->DrawIndexedPrimitive(type, mVertexBufferPt - mVertexBufferLockSize + vertStart, 0, vertCount, mIndexBufferPt - mIndexBufferLockSize + idxStart, primCount);

	VDASSERT(SUCCEEDED(hr));

	return hr;
}

HRESULT VDD3D9Manager::Present(const RECT *src, HWND hwndDest, bool vsync, float& syncdelta, VDD3DPresentHistory& history) {
	if (!mPresentParms.Windowed)
		return S_OK;

	HRESULT hr;

	if (vsync && (mDevCaps.Caps & D3DCAPS_READ_SCANLINE)) {
		if (mpD3DQuery) {
			hr = mpD3DQuery->Issue(D3DISSUE_END);
			if (SUCCEEDED(hr)) {
				while(S_FALSE == mpD3DQuery->GetData(NULL, 0, D3DGETDATA_FLUSH))
					::Sleep(1);
			}
		}

		RECT r;
		if (GetWindowRect(hwndDest, &r)) {
			int top = 0;
			int bottom = GetSystemMetrics(SM_CYSCREEN);

			// GetMonitorInfo() requires Windows 98. We might never fail on this because
			// I think DirectX 9.0c requires 98+, but we have to dynamically link anyway
			// to avoid a startup link failure on 95.
			typedef BOOL (APIENTRY *tpGetMonitorInfo)(HMONITOR mon, LPMONITORINFO lpmi);
			static tpGetMonitorInfo spGetMonitorInfo = (tpGetMonitorInfo)GetProcAddress(GetModuleHandle("user32"), "GetMonitorInfo");

			if (spGetMonitorInfo) {
				HMONITOR hmon = mpD3D->GetAdapterMonitor(mAdapter);
				MONITORINFO monInfo = {sizeof(MONITORINFO)};
				if (spGetMonitorInfo(hmon, &monInfo)) {
					top = monInfo.rcMonitor.top;
					bottom = monInfo.rcMonitor.bottom;
				}
			}

			if (r.top < top)
				r.top = top;
			if (r.bottom > bottom)
				r.bottom = bottom;

			top += VDRoundToInt(history.mPresentDelay);

			r.top -= top;
			r.bottom -= top;

			// Poll raster status, and wait until we can safely blit. We assume that the
			// blit can outrace the beam. 
			D3DRASTER_STATUS rastStatus;
			UINT maxScanline = 0;
			int firstScan = -1;
			while(SUCCEEDED(mpD3DDevice->GetRasterStatus(0, &rastStatus))) {
				if (firstScan < 0)
					firstScan = rastStatus.InVBlank ? 0 : (int)rastStatus.ScanLine;

				if (rastStatus.InVBlank) {
					if (history.mVBlankSuccess >= 0.5f)
						break;

					rastStatus.ScanLine = 0;
				}

				// Check if we have wrapped around without seeing the VBlank. If this
				// occurs, force an exit. This prevents us from potentially burning a lot
				// of CPU time if the CPU becomes busy and can't poll the beam in a timely
				// manner.
				if (rastStatus.ScanLine < maxScanline)
					break;

				// Check if we're outside of the danger zone.
				if ((int)rastStatus.ScanLine < r.top || (int)rastStatus.ScanLine >= r.bottom)
					break;

				// We're in the danger zone. If the delta is greater than one tenth of the
				// display, do a sleep.
				if ((r.bottom - (int)rastStatus.ScanLine) * 10 >= (int)mDisplayMode.Height)
					::Sleep(1);

				maxScanline = rastStatus.ScanLine;
			}

			syncdelta = (float)(firstScan - r.bottom) / (float)(int)mDisplayMode.Height;
			syncdelta -= floorf(syncdelta);
			if (syncdelta > 0.5f)
				syncdelta -= 1.0f;

			hr = mpD3DDevice->Present(src, NULL, hwndDest, NULL);
			if (FAILED(hr))
				return hr;

			D3DRASTER_STATUS rastStatus2;
			hr = mpD3DDevice->GetRasterStatus(0, &rastStatus2);
			if (SUCCEEDED(hr)) {
				if (rastStatus.InVBlank) {
					float success = rastStatus2.InVBlank || (int)rastStatus2.ScanLine <= r.top || (int)rastStatus2.ScanLine >= r.bottom ? 1.0f : 0.0f;

					history.mVBlankSuccess += (success - history.mVBlankSuccess) * 0.01f;
				}

				if (!rastStatus.InVBlank && !rastStatus2.InVBlank && rastStatus2.ScanLine > rastStatus.ScanLine) {
					float delta = (float)(int)(rastStatus2.ScanLine - rastStatus.ScanLine);

					history.mPresentDelay += (delta - history.mPresentDelay) * 0.01f;
				}
			}
		}
	} else {
		syncdelta = 0.0f;

		hr = mpD3DDevice->Present(src, NULL, hwndDest, NULL);
	}

	return hr;
}

HRESULT VDD3D9Manager::PresentFullScreen(bool wait) {
	if (mPresentParms.Windowed)
		return S_OK;

	HRESULT hr;
	IDirect3DSwapChain9 *pSwapChain;
	hr = mpD3DDevice->GetSwapChain(0, &pSwapChain);
	if (FAILED(hr))
		return hr;

	for(;;) {
		hr = pSwapChain->Present(NULL, NULL, NULL, NULL, D3DPRESENT_DONOTWAIT);

		if (SUCCEEDED(hr) || hr != D3DERR_WASSTILLDRAWING)
			break;

		if (!wait)
			return S_FALSE;

		::Sleep(1);
	}

	// record raster status and time of this present
	mLastPresentTime = VDGetAccurateTick();
	mLastPresentScanLine = 0;
	if (mDevCaps.Caps & D3DCAPS_READ_SCANLINE) {
		D3DRASTER_STATUS rastStatus;
		hr = mpD3DDevice->GetRasterStatus(0, &rastStatus);
		if (SUCCEEDED(hr) && !rastStatus.InVBlank)
			mLastPresentScanLine = rastStatus.InVBlank;
	}

	pSwapChain->Release();

	return hr;
}

#define REQUIRE(x, reason) if (!(x)) { VDDEBUG_D3D("VideoDisplay/DX9: 3D device is lame -- reason: " reason "\n"); return true; } else ((void)0)
#define REQUIRECAPS(capsflag, bits, reason) REQUIRE(!(~mDevCaps.capsflag & (bits)), reason)

bool VDD3D9Manager::Is3DCardLame() {
	REQUIRE(mDevCaps.DeviceType != D3DDEVTYPE_SW, "software device detected");
	REQUIRECAPS(PrimitiveMiscCaps, D3DPMISCCAPS_CULLNONE, "primitive misc caps check failed");
	REQUIRECAPS(RasterCaps, D3DPRASTERCAPS_DITHER, "raster caps check failed");
	REQUIRECAPS(TextureCaps, D3DPTEXTURECAPS_ALPHA | D3DPTEXTURECAPS_MIPMAP, "texture caps failed");
	REQUIRE(!(mDevCaps.TextureCaps & D3DPTEXTURECAPS_SQUAREONLY), "device requires square textures");
	REQUIRECAPS(TextureFilterCaps, D3DPTFILTERCAPS_MAGFPOINT | D3DPTFILTERCAPS_MAGFLINEAR
								| D3DPTFILTERCAPS_MINFPOINT | D3DPTFILTERCAPS_MINFLINEAR
								| D3DPTFILTERCAPS_MIPFPOINT | D3DPTFILTERCAPS_MIPFLINEAR, "texture filtering modes insufficient");
	REQUIRECAPS(TextureAddressCaps, D3DPTADDRESSCAPS_CLAMP | D3DPTADDRESSCAPS_WRAP, "texture addressing modes insufficient");
	REQUIRE(mDevCaps.MaxTextureBlendStages>0 && mDevCaps.MaxSimultaneousTextures>0, "not enough texture stages");
	return false;
}

bool VDD3D9Manager::CreateSharedTexture(const char *name, SharedTextureFactory factory, IVDD3D9Texture **ppTexture) {
	SharedTextures::iterator it(mSharedTextures.begin()), itEnd(mSharedTextures.end());
	for(; it!=itEnd; ++it) {
		VDD3D9Texture& texture = **it;

		if (!strcmp(texture.GetName(), name)) {
			*ppTexture = &texture;
			texture.AddRef();
			return true;
		}
	}

	vdrefptr<IVDD3D9TextureGenerator> pGenerator;
	if (factory) {
		if (!factory(~pGenerator))
			return false;
	}

	vdrefptr<VDD3D9Texture> pTexture(new_nothrow VDD3D9Texture);
	if (!pTexture)
		return false;

	pTexture->SetName(name);

	if (!pTexture->Init(this, pGenerator))
		return false;

	mSharedTextures.push_back(pTexture);

	*ppTexture = pTexture.release();
	return true;
}

bool VDD3D9Manager::CreateSwapChain(int width, int height, IVDD3D9SwapChain **ppSwapChain) {
	D3DPRESENT_PARAMETERS pparms={};

	pparms.Windowed			= TRUE;
	pparms.SwapEffect		= D3DSWAPEFFECT_COPY;
	pparms.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
	pparms.BackBufferWidth	= width;
	pparms.BackBufferHeight	= height;
	pparms.BackBufferCount	= 1;
	pparms.BackBufferFormat	= mPresentParms.BackBufferFormat;

	vdrefptr<IDirect3DSwapChain9> pD3DSwapChain;
	HRESULT hr = mpD3DDevice->CreateAdditionalSwapChain(&pparms, ~pD3DSwapChain);
	if (FAILED(hr))
		return false;

	vdrefptr<VDD3D9SwapChain> pSwapChain(new_nothrow VDD3D9SwapChain(pD3DSwapChain));
	if (!pSwapChain)
		return false;

	*ppSwapChain = pSwapChain.release();
	return true;
}

void VDD3D9Manager::SetSwapChainActive(IVDD3D9SwapChain *pSwapChain) {
	if (!pSwapChain)
		pSwapChain = mpImplicitSwapChain;

	IDirect3DSwapChain9 *pD3DSwapChain = static_cast<VDD3D9SwapChain *>(pSwapChain)->GetD3DSwapChain();

	IDirect3DSurface9 *pD3DBackBuffer;
	HRESULT hr = pD3DSwapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &pD3DBackBuffer);
	if (FAILED(hr))
		return;

	hr = mpD3DDevice->SetRenderTarget(0, pD3DBackBuffer);
	pD3DBackBuffer->Release();
}

HRESULT VDD3D9Manager::PresentSwapChain(IVDD3D9SwapChain *pSwapChain, const RECT *srcRect, HWND hwndDest, bool vsync, bool newframe, bool donotwait, float& syncDelta, VDD3DPresentHistory& history) {
	if (!mPresentParms.Windowed)
		return S_OK;

	if (!pSwapChain) {
		pSwapChain = mpImplicitSwapChain;
		if (!pSwapChain)
			return E_FAIL;
	}

	IDirect3DSwapChain9 *pD3DSwapChain = static_cast<VDD3D9SwapChain *>(pSwapChain)->GetD3DSwapChain();
	HRESULT hr;

	if (!vsync || !(mDevCaps.Caps & D3DCAPS_READ_SCANLINE)) {
		if (!newframe)
			return S_OK;

		syncDelta = 0.0f;

		hr = pD3DSwapChain->Present(srcRect, NULL, hwndDest, NULL, 0);
		history.mbPresentPending = false;
		return hr;
	}

	// Okay, now we know we're doing vsync.
	if (newframe && !history.mbPresentPending) {
		RECT r;
		if (!GetWindowRect(hwndDest, &r))
			return E_FAIL;

		int top = 0;
		int bottom = GetSystemMetrics(SM_CYSCREEN);

		// GetMonitorInfo() requires Windows 98. We might never fail on this because
		// I think DirectX 9.0c requires 98+, but we have to dynamically link anyway
		// to avoid a startup link failure on 95.
		typedef BOOL (APIENTRY *tpGetMonitorInfo)(HMONITOR mon, LPMONITORINFO lpmi);
		static tpGetMonitorInfo spGetMonitorInfo = (tpGetMonitorInfo)GetProcAddress(GetModuleHandle("user32"), "GetMonitorInfo");

		if (spGetMonitorInfo) {
			HMONITOR hmon = mpD3D->GetAdapterMonitor(mAdapter);
			MONITORINFO monInfo = {sizeof(MONITORINFO)};
			if (spGetMonitorInfo(hmon, &monInfo)) {
				top = monInfo.rcMonitor.top;
				bottom = monInfo.rcMonitor.bottom;
			}
		}

		if (r.top < top)
			r.top = top;
		if (r.bottom > bottom)
			r.bottom = bottom;

		r.top -= top;
		r.bottom -= top;

		history.mScanTop = r.top;
		history.mScanBottom = r.bottom;

		newframe = false;
		history.mbPresentPending = true;
		history.mbPresentBlitStarted = false;

		history.mLastScanline = -1;
		history.mPresentStartTime = VDGetPreciseTick();
	}

	if (!history.mbPresentPending)
		return S_OK;

	// Poll raster status, and wait until we can safely blit. We assume that the
	// blit can outrace the beam. 
	++history.mPollCount;
	for(;;) {
		// if we've already started the blit, skip beam-following
		if (history.mbPresentBlitStarted)
			break;

		D3DRASTER_STATUS rastStatus;
		hr = pD3DSwapChain->GetRasterStatus(&rastStatus);
		if (FAILED(hr))
			return hr;

		if (rastStatus.InVBlank)
			rastStatus.ScanLine = 0;

		sint32 y1 = (sint32)history.mLastScanline;
		if (y1 < 0) {
			y1 = rastStatus.ScanLine;
			history.mAverageStartScanline += ((float)y1 - history.mAverageStartScanline) * 0.01f;
		}

		sint32 y2 = (sint32)rastStatus.ScanLine;

		history.mbLastWasVBlank	= rastStatus.InVBlank ? true : false;
		history.mLastScanline	= rastStatus.ScanLine;

		sint32 yt = (sint32)history.mScanlineTarget;

		history.mLastBracketY1 = y1;
		history.mLastBracketY2 = y2;

		// check for yt in [y1, y2]... but we have to watch for a beam wrap (y1 > y2).
		if (y1 <= y2) {
			// non-wrap case
			if (y1 <= yt && yt <= y2)
				break;
		} else {
			// wrap case
			if (y1 <= yt || yt <= y2)
				break;
		}

		if (donotwait)
			return S_FALSE;

		::Sleep(1);
	}

	history.mbPresentBlitStarted = true;

	if (donotwait) {
		hr = pD3DSwapChain->Present(srcRect, NULL, hwndDest, NULL, D3DPRESENT_DONOTWAIT);

		if (hr == D3DERR_WASSTILLDRAWING)
			return S_FALSE;
	} else
		hr = pD3DSwapChain->Present(srcRect, NULL, hwndDest, NULL, 0);

	history.mbPresentPending = false;
	if (FAILED(hr))
		return hr;

	history.mAverageEndScanline += ((float)history.mLastScanline - history.mAverageEndScanline) * 0.01f;
	history.mAveragePresentTime += ((VDGetPreciseTick() - history.mPresentStartTime)*VDGetPreciseSecondsPerTick() - history.mAveragePresentTime) * 0.01f;

	D3DRASTER_STATUS rastStatus2;
	hr = pD3DSwapChain->GetRasterStatus(&rastStatus2);
	syncDelta = 0.0f;
	if (SUCCEEDED(hr)) {
		if (rastStatus2.InVBlank)
			rastStatus2.ScanLine = 0;

		float yf = ((float)rastStatus2.ScanLine - (float)history.mScanTop) / ((float)history.mScanBottom - (float)history.mScanTop);

		yf -= 0.2f;

		if (yf < 0.0f)
			yf = 0.0f;
		if (yf > 1.0f)
			yf = 1.0f;

		if (yf > 0.5f)
			yf -= 1.0f;

		syncDelta = yf;

		history.mScanlineTarget -= yf * 15.0f;
		if (history.mScanlineTarget < 0.0f)
			history.mScanlineTarget += (float)mDisplayMode.Height;
		else if (history.mScanlineTarget >= (float)mDisplayMode.Height)
			history.mScanlineTarget -= (float)mDisplayMode.Height;

		float success = rastStatus2.InVBlank || (int)rastStatus2.ScanLine <= history.mScanTop || (int)rastStatus2.ScanLine >= history.mScanBottom ? 1.0f : 0.0f;

		int zone = 0;
		if (!history.mbLastWasVBlank)
			zone = ((int)history.mLastScanline * 16) / (int)mDisplayMode.Height;

		for(int i=0; i<17; ++i) {
			if (i != zone)
				history.mAttemptProb[i] *= 0.99f;
		}

		history.mAttemptProb[zone] += (1.0f - history.mAttemptProb[zone]) * 0.01f;
		history.mSuccessProb[zone] += (success - history.mSuccessProb[zone]) * 0.01f;

		if (history.mLastScanline < history.mScanTop) {
			history.mVBlankSuccess += (success - history.mVBlankSuccess) * 0.01f;
		}

		if (!history.mbLastWasVBlank && !rastStatus2.InVBlank && rastStatus2.ScanLine > history.mLastScanline) {
			float delta = (float)(int)(rastStatus2.ScanLine - history.mLastScanline);

			history.mPresentDelay += (delta - history.mPresentDelay) * 0.01f;
		}
	}

	return hr;
}

LRESULT CALLBACK VDD3D9Manager::StaticDeviceWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
	switch(msg) {
		case WM_SETFOCUS:
			{
				HWND hwndOldFocus = (HWND)wParam;

				// Direct3D likes to send focus to the focus window when you go full screen. Well,
				// we don't want that since we're using a hidden window, so if the focus came from
				// another window of ours, we send the focus back.
				if (hwndOldFocus) {
					DWORD pid;
					DWORD tid = GetWindowThreadProcessId(hwndOldFocus, &pid);

					if (tid == GetCurrentThreadId()) {
						SetFocus(hwndOldFocus);
						return 0;
					}
				}
			}
			break;
	}

	return DefWindowProc(hwnd, msg, wParam, lParam);
}
